/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.editor.options; import java.beans.*; import java.awt.event.*; import java.awt.Dialog; import java.awt.Component; import javax.swing.*; import java.text.MessageFormat; import java.util.*; import org.openide.*; import org.openide.util.NbBundle; import org.netbeans.editor.*; /** * @author Petr Nejedly */ public class KeyBindingsEditorPanel extends javax.swing.JPanel { private static ResourceBundle bundle = NbBundle.getBundle( KeyBindingsEditorPanel.class ); private ActionDescriptor[] acts; private int actionIndex; private String kitClassName; private KeyBindingsEditor editor; private String defaultActionName; /** Creates new form KeyBindingsEditorPanel */ public KeyBindingsEditorPanel( KeyBindingsEditor editor ) { this.editor = editor; initComponents (); } /** * Sets the current editorKit and action->Vector(KeyStroke[]) mapping. * Note: first item points to proper EditorKit class. */ public void setValue( List l ) { kitClassName = (String)l.get( 0 ); Class kitClass = null; try { kitClass = Class.forName( kitClassName ); } catch( ClassNotFoundException e ) { if( Boolean.getBoolean( "netbeans.debug.exceptions" ) ) e.printStackTrace(); return; } defaultActionName = null; // Get all actions available in given kit, sort them and store their // ActionDescriptors. Prepare mapping for looking them up by their names. Action[] actions = BaseKit.getKit( kitClass ).getActions(); // Create our sorter, ActionDescriptors knows themselves how to sort TreeMap treeMap = new TreeMap( ); // Fill it with new ActionDescriptors for actions, they'll be in-sorted for( int i=0; i<actions.length; i++ ) { ActionDescriptor val = new ActionDescriptor( actions[i] ); treeMap.put( val.name, val ); } // add all inherited bindings Class parent = kitClass.getSuperclass(); Settings.KitAndValue[] kv = Settings.getKitAndValueArray( parent, Settings.KEY_BINDING_LIST ); // go through all levels and add inherited bindings for( int i=kv.length - 1; i >= 0; i--) addKeyBindingList( treeMap, ((List)kv[i].value).iterator(), true ); // add bindings of current kit - couple ActionDescriptors with proper KeySequences addKeyBindingList( treeMap, l.listIterator( 1 ), false ); // Create our sorted list of ActionDescriptors acts = (ActionDescriptor[])treeMap.values().toArray( new ActionDescriptor[0] ); // do we have anything to manage? if( acts.length > 0 ) addSequenceButton.setEnabled( true ); actionsList.setListData( acts ); actionsList.setSelectedIndex( actionIndex ); updateSequences( 0 ); } private void addKeyBindingList( Map target, Iterator source, boolean inherited ) { while( source.hasNext() ) { MultiKeyBinding b = (MultiKeyBinding)source.next(); ActionDescriptor ad = (ActionDescriptor)target.get( b.actionName ); if( ad != null ) { // we've found proper action KeySequence sequence = getKeySequenceForBinding( inherited, b ); if( sequence == null ) { if( !inherited ) defaultActionName = b.actionName; } else { ad.sequences.add( sequence ); } } else { // complain for weird mapping //System.err.println( "Weird mapping" ); } } } private KeySequence getKeySequenceForBinding( boolean inherited, MultiKeyBinding binding ) { KeyStroke[] sequence = binding.keys; if( sequence == null ) { // convert simple KeyStroke to KeyStroke[1] if( binding.key == null ) return null; sequence = new KeyStroke[1]; sequence[0] = binding.key; } return new KeySequence( inherited, sequence ); } /** * Return the list of MultiKeyBindings */ public List getValue() { Vector val = new Vector(); // add the kitClass of current kit val.add( kitClassName ); // add default action if we have one if( defaultActionName != null) val.add( new MultiKeyBinding( (KeyStroke)null, defaultActionName ) ); // go through whole array of Actions and add all KeySequences for every Action for( int i=0; i<acts.length; i++ ) { String name = acts[i].name; for( Iterator iter=acts[i].sequences.iterator(); iter.hasNext(); ) { KeySequence seq = (KeySequence)iter.next(); if( !seq.isInherited() ) { // add only our bindings, not inherited val.add( new MultiKeyBinding( seq.getKeyStrokes(), name ) ); } } } // that's it, done return val; } // index tells which sequence to select private void updateSequences( int index ) { Vector bindings = acts[actionIndex].sequences; // reflect the change in actionIndex or actual sequenceList sequencesList.setListData( bindings ); // select proper line, this will also fire ValueChanged on sequencesList if( bindings.size() > 0 ) sequencesList.setSelectedIndex( index ); } // index tells which sequence is selected - to which we are bound private void updateRemoveButton() { int id = sequencesList.getSelectedIndex() ; Vector b = acts[actionIndex].sequences; boolean enable = id >= 0 && id < b.size() && !((KeySequence)b.get( id )).isInherited(); removeSequenceButton.setEnabled( enable ); } private void notifyEditor() { if( editor != null ) editor.customEditorChange(); } /** * Create our visual representation. */ private void initComponents () {//GEN-BEGIN:initComponents actionsPanel = new javax.swing.JPanel (); actionsScrollPane = new javax.swing.JScrollPane (); actionsList = new javax.swing.JList (); sequencesPanel = new javax.swing.JPanel (); sequencesScrollPane = new javax.swing.JScrollPane (); sequencesList = new javax.swing.JList (); addSequenceButton = new javax.swing.JButton (); removeSequenceButton = new javax.swing.JButton (); setLayout (new javax.swing.BoxLayout (this, 1)); setBorder (new javax.swing.border.EmptyBorder(new java.awt.Insets(8, 8, 8, 8))); actionsPanel.setLayout (new javax.swing.BoxLayout (actionsPanel, 0)); actionsPanel.setBorder (new javax.swing.border.CompoundBorder( new javax.swing.border.TitledBorder( bundle.getString( "KBEP_Actions" ) ), new javax.swing.border.EmptyBorder(new java.awt.Insets(8, 8, 8, 8) ) )); actionsList.addListSelectionListener (new javax.swing.event.ListSelectionListener () { public void valueChanged (javax.swing.event.ListSelectionEvent evt) { actionsListValueChanged (evt); } } ); actionsScrollPane.setViewportView (actionsList); actionsPanel.add (actionsScrollPane); add (actionsPanel); sequencesPanel.setLayout (new java.awt.GridBagLayout ()); java.awt.GridBagConstraints gridBagConstraints1; sequencesPanel.setBorder (new javax.swing.border.CompoundBorder( new javax.swing.border.TitledBorder( bundle.getString( "KBEP_Sequences" ) ), new javax.swing.border.EmptyBorder( new java.awt.Insets( 8, 8, 8, 8 ) ) )); sequencesList.setCellRenderer (new KeySequenceCellRenderer()); sequencesList.addListSelectionListener (new javax.swing.event.ListSelectionListener () { public void valueChanged (javax.swing.event.ListSelectionEvent evt) { sequencesListValueChanged (evt); } } ); sequencesScrollPane.setViewportView (sequencesList); gridBagConstraints1 = new java.awt.GridBagConstraints (); gridBagConstraints1.gridheight = 3; gridBagConstraints1.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints1.insets = new java.awt.Insets (0, 0, 0, 8); gridBagConstraints1.weightx = 1.0; gridBagConstraints1.weighty = 1.0; sequencesPanel.add (sequencesScrollPane, gridBagConstraints1); addSequenceButton.setText (bundle.getString( "KBEP_Add" )); addSequenceButton.setEnabled (false); addSequenceButton.addActionListener (new java.awt.event.ActionListener () { public void actionPerformed (java.awt.event.ActionEvent evt) { addSequenceButtonActionPerformed (evt); } } ); gridBagConstraints1 = new java.awt.GridBagConstraints (); gridBagConstraints1.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints1.insets = new java.awt.Insets (0, 0, 5, 0); sequencesPanel.add (addSequenceButton, gridBagConstraints1); removeSequenceButton.setText (bundle.getString( "KBEP_Remove" )); removeSequenceButton.setEnabled (false); removeSequenceButton.addActionListener (new java.awt.event.ActionListener () { public void actionPerformed (java.awt.event.ActionEvent evt) { removeSequenceButtonActionPerformed (evt); } } ); gridBagConstraints1 = new java.awt.GridBagConstraints (); gridBagConstraints1.gridx = 1; gridBagConstraints1.gridy = 1; gridBagConstraints1.fill = java.awt.GridBagConstraints.HORIZONTAL; sequencesPanel.add (removeSequenceButton, gridBagConstraints1); add (sequencesPanel); }//GEN-END:initComponents private void sequencesListValueChanged (javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_sequencesListValueChanged updateRemoveButton(); }//GEN-LAST:event_sequencesListValueChanged private void addSequenceButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addSequenceButtonActionPerformed // Create KeySequence input dialog and ask user for value KeySequence newSequence = new KeySequenceRequester().getKeySequence(); // If user canceled action, stop entering if( newSequence == null ) return; // Add new KeySequence to proper list acts[actionIndex].sequences.add( newSequence ); // Render and select the last added item updateSequences( acts[actionIndex].sequences.size()-1 ); notifyEditor(); }//GEN-LAST:event_addSequenceButtonActionPerformed private void removeSequenceButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeSequenceButtonActionPerformed // Remove selected sequences from sequenceList int index = sequencesList.getSelectedIndex(); if( index >= 0 ) { acts[actionIndex].sequences.remove( index ); if( index >= acts[actionIndex].sequences.size() ) index--; updateSequences( index ); notifyEditor(); } }//GEN-LAST:event_removeSequenceButtonActionPerformed private void actionsListValueChanged (javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_actionsListValueChanged actionIndex = actionsList.getSelectedIndex(); updateSequences( 0 ); }//GEN-LAST:event_actionsListValueChanged // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel actionsPanel; private javax.swing.JScrollPane actionsScrollPane; private javax.swing.JList actionsList; private javax.swing.JPanel sequencesPanel; private javax.swing.JScrollPane sequencesScrollPane; private javax.swing.JList sequencesList; private javax.swing.JButton addSequenceButton; private javax.swing.JButton removeSequenceButton; // End of variables declaration//GEN-END:variables /** * Encapsulation for components of dialog asking for new KeySequence */ private class KeySequenceRequester { KeySequenceInputPanel input; DialogDescriptor dd; Dialog dial; Object[] buttons = { new JButton( bundle.getString( "KBEP_OK_LABEL" ) ), new JButton( bundle.getString( "KBEP_CLEAR_LABEL" ) ), DialogDescriptor.CANCEL_OPTION }; KeySequence retVal = null; KeySequenceRequester() { ((JButton)buttons[0]).setEnabled( false ); // default initial state // Prepare KeySequence input dialog input = new KeySequenceInputPanel(); input.addPropertyChangeListener( new PropertyChangeListener() { public void propertyChange( PropertyChangeEvent evt ) { if( KeySequenceInputPanel.PROP_KEYSEQUENCE != evt.getPropertyName() ) return; KeyStroke[] seq = input.getKeySequence(); String warn = getCollisionString( seq ); input.setInfoText( warn == null ? "" : warn ); // NOI18N ((JButton)buttons[0]).setEnabled( warn == null ); } } ); dd = new DialogDescriptor ( input, bundle.getString( "KBEP_AddSequence" ), true, buttons, buttons[0], DialogDescriptor.BOTTOM_ALIGN, null, new ActionListener(){ public void actionPerformed( ActionEvent evt ) { if( evt.getSource() == buttons[1] ) { // Clear pressed input.clear(); // Clear entered KeyStrokes, start again input.requestFocus(); // Make user imediately able to enter new strokes } else if( evt.getSource() == buttons[0] ) { // OK pressed retVal = new KeySequence( false, input.getKeySequence() ); dial.dispose(); // Done } } }); } KeySequence getKeySequence() { dial = TopManager.getDefault().createDialog(dd); input.requestFocus(); // Place caret in it, hopefully dial.show(); // let the user tell us their wish, result will be stored in retVal return retVal; } String getCollisionString( KeyStroke[] seq ) { if( seq.length == 0 ) return ""; // NOI18N not valid sequence, but don't alert user for( int i=0; i<acts.length; i++ ) { // for all actions Iterator iter = acts[i].sequences.iterator(); while( iter.hasNext() ) { KeyStroke[] s1 = ((KeySequence)iter.next()).getKeyStrokes(); if( isOverlapingSequence( s1, seq ) ) { Object[] values = { KeySequenceInputPanel.keySequenceToString( s1 ), acts[i] }; return MessageFormat.format( bundle.getString( "KBEP_FMT_Collision" ), values ); } } } return null; // no colliding sequence } private boolean isOverlapingSequence( KeyStroke[] s1, KeyStroke[] s2 ) { int l = Math.min( s1.length, s2.length ); while( l-- > 0 ) if( !s1[l].equals( s2[l] ) ) return false; return true; } } /** * Information holder class for Action, it knows it's Action name, which * sequences is this Action bound to, and how to correctly present * it as String (it's displayName ). * It also knows how to sort it's instances (Comparable) * As it is private, all members could be directly read. */ private static final class ActionDescriptor implements Comparable { String name; String displayName; Vector sequences; ActionDescriptor( Action a ) { name = (String)a.getValue( Action.NAME ); displayName = a.getValue( Action.SHORT_DESCRIPTION ) + " [" + name + "]"; // NOI18N sequences = new Vector(); } public String toString() { return displayName; } // Naturaly ordered by its name public int compareTo( Object o ) { return name.compareTo( ((ActionDescriptor)o).name ); } } /** * Container class for KeyStroke[], which knows if this KeyStroke is inherited * and how to correctly present it as String. */ private final static class KeySequence { private boolean inherited; private KeyStroke[] sequence; KeySequence( boolean inherited, KeyStroke[] sequence) { this.inherited = inherited; this.sequence = sequence; } KeyStroke[] getKeyStrokes() { return sequence; } boolean isInherited() { return inherited; } public String toString() { return KeySequenceInputPanel.keySequenceToString( sequence ); } } /** * Special cell renderer for sequencesList, which renders inherited KeySequences * differently from not inherited to visually notify user which sequences * couldn't be removed. */ private final static class KeySequenceCellRenderer extends JLabel implements ListCellRenderer { public KeySequenceCellRenderer() { setOpaque( true ); } public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus ) { setText( value.toString() ); setBackground( isSelected ? list.getSelectionBackground() : list.getBackground() ); if( (value instanceof KeySequence) && ((KeySequence)value).isInherited() ) setForeground( java.awt.Color.gray ); else setForeground( isSelected ? list.getSelectionForeground() : list.getForeground() ); setEnabled(list.isEnabled()); setFont(list.getFont()); return this; } } } /* * Log * 6 Gandalf-post-FCS1.5 3/20/00 Miloslav Metelka renaming * 5 Gandalf-post-FCS1.4 3/17/00 Petr Nejedly Rolled back to compile * under post-FCS * 4 Gandalf-post-FCS1.3 3/16/00 Miloslav Metelka renamings * 3 Gandalf-post-FCS1.2 3/15/00 Miloslav Metelka reverted previous * version - ST error? * 2 Gandalf-post-FCS1.1 3/15/00 Miloslav Metelka * 1 Gandalf-post-FCS1.0 2/28/00 Petr Nejedly initial revision * $ */